package org.force4spring.support;

import java.rmi.RemoteException;

import javax.xml.rpc.ServiceException;

import net.sf.ehcache.Cache;

import org.force4spring.PasswordExpiredException;
import org.springframework.beans.factory.InitializingBean;

import com.sforce.soap.partner.CallOptions;
import com.sforce.soap.partner.LoginResult;
import com.sforce.soap.partner.SessionHeader;
import com.sforce.soap.partner.SforceServiceLocator;
import com.sforce.soap.partner.SoapBindingStub;

public class DefaultSessionFactory implements SessionFactory, InitializingBean {
	private SforceServiceLocator serviceLocator;
	private String loginServerUrl;
	private ForceCredentialsResolver credentialsResolver;
	private int timeout = 10000;
	private Cache sessionCache;
	private String clientId;
	private String defaultNamespace;

	/**
	 * Returns value of 'serviceLocator' property
	 * 
	 * @return serviceLocator property
	 */
	public SforceServiceLocator getServiceLocator() {
		return serviceLocator;
	}

	/**
	 * Sets value of 'serviceLocator' proprty to specified value.
	 * 
	 * @param serviceLocator
	 *            New value.
	 */
	public void setServiceLocator(SforceServiceLocator serviceLocator) {
		this.serviceLocator = serviceLocator;
	}

	public ForceSession openSession(ForceCredentials credentials)
			throws RemoteException, ServiceException {
		return openSession(credentials, getTimeout());
	}

	public ForceSession openSession() throws RemoteException, ServiceException {
		return openSession(getTimeout());
	}

	public ForceSession openSession(int timeout) throws RemoteException,
			ServiceException {
		ForceCredentials credentials = credentialsResolver.resolve();
		if (credentials == null) {
			throw new IllegalArgumentException(
					"Unable to resolve credentials to open Force.com session");
		}
		return openSession(credentials, timeout);
	}

	public ForceSession openSession(ForceCredentials credentials, int timeout)
			throws RemoteException, ServiceException {
		String serverUrl = credentials.getServerUrl();
		String sessionId = credentials.getSessionId();
		String username = credentials.getUsername();
		String password = credentials.getPassword();

		SoapBindingStub binding = null;
		if ((!isBlank(credentials.getServerUrl())) && (!isBlank(sessionId))) {
			binding = loginWithSession(serverUrl, sessionId, timeout);
		} else {
			binding = loginWithUsernamePassword(username, password, timeout);
		}
		return new ForceSessionImpl(binding, credentials, defaultNamespace);
	}

	private boolean isBlank(String value) {
		return ((value == null) || (value.trim().length() == 0));
	}

	protected SoapBindingStub loginWithSession(String serverUrl,
			String sessionId, int timeout) throws ServiceException {
		SoapBindingStub binding = (SoapBindingStub) getServiceLocator()
				.getSoap();
		binding.setTimeout(timeout);

		binding._setProperty(SoapBindingStub.ENDPOINT_ADDRESS_PROPERTY,
				serverUrl);

		SessionHeader sh = new SessionHeader();
		sh.setSessionId(sessionId);
		binding.setHeader(new SforceServiceLocator().getServiceName()
				.getNamespaceURI(), "SessionHeader", sh);
		
		if ((clientId != null) || (defaultNamespace != null)) {
			CallOptions co = new CallOptions();
			if (clientId != null) {
				co.setClient(clientId);
			}
			
			if (defaultNamespace != null) {
				co.setDefaultNamespace(defaultNamespace);
			}
			binding.setHeader(new SforceServiceLocator().getServiceName()
					.getNamespaceURI(), "CallOptions", co);
		}
		
		return binding;
	}

	protected SoapBindingStub loginWithUsernamePassword(String username,
			String password, int timeout) throws RemoteException,
			ServiceException {
		SoapBindingStub binding = (SoapBindingStub) getServiceLocator()
				.getSoap();
		if (loginServerUrl != null) {
			binding._setProperty(SoapBindingStub.ENDPOINT_ADDRESS_PROPERTY,
					loginServerUrl);
		}
		binding.setTimeout(timeout);
		
		if (clientId != null) {
			CallOptions co = new CallOptions();
			co.setClient(clientId);
			binding.setHeader(new SforceServiceLocator().getServiceName()
					.getNamespaceURI(), "CallOptions", co);
		}
		
		LoginResult result = binding.login(username, password);
		if (result.isPasswordExpired()) {
			throw new PasswordExpiredException(username);
		}
		String serverUrl = result.getServerUrl();
		String sessionId = result.getSessionId();
		return loginWithSession(serverUrl, sessionId, timeout);
	}

	/**
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */
	public void afterPropertiesSet() throws Exception {
		if (getServiceLocator() == null) {
			throw new IllegalArgumentException(
					"Property 'serviceLocator' is required");
		}
		if (getCredentialsResolver() == null) {
			throw new IllegalArgumentException(
					"Property 'credentialsResolver' is required");
		}

	}

	/**
	 * Returns value of 'defaultTimeout' property
	 * 
	 * @return defaultTimeout property
	 */
	public int getTimeout() {
		return timeout;
	}

	/**
	 * Sets value of 'defaultTimeout' proprty to specified value.
	 * 
	 * @param defaultTimeout
	 *            New value.
	 */
	public void setTimeout(int defaultTimeout) {
		this.timeout = defaultTimeout;
	}

	/**
	 * Returns value of 'sessionCache' property
	 * 
	 * @return sessionCache property
	 */
	public Cache getSessionCache() {
		return sessionCache;
	}

	/**
	 * Sets value of 'sessionCache' proprty to specified value.
	 * 
	 * @param sessionCache
	 *            New value.
	 */
	public void setSessionCache(Cache sessionCache) {
		this.sessionCache = sessionCache;
	}

	/**
	 * Returns value of 'credentialsResolver' property
	 * 
	 * @return credentialsResolver property
	 */
	public ForceCredentialsResolver getCredentialsResolver() {
		return credentialsResolver;
	}

	/**
	 * Sets value of 'credentialsResolver' proprty to specified value.
	 * 
	 * @param credentialsResolver
	 *            New value.
	 */
	public void setCredentialsResolver(
			ForceCredentialsResolver credentialsResolver) {
		this.credentialsResolver = credentialsResolver;
	}

	public String getLoginServerUrl() {
		return loginServerUrl;
	}

	public void setLoginServerUrl(String loginServerUrl) {
		this.loginServerUrl = loginServerUrl;
	}

	/**
	 * Returns value of 'clientId' property
	 * 
	 * @return clientId property
	 */
	public String getClientId() {
		return clientId;
	}

	/**
	 * Sets value of 'clientId' property to specified value.
	
	 * @param clientId New value.
	 */
	public void setClientId(String clientId) {
		this.clientId = clientId;
	}

	/**
	 * Returns value of 'defaultNamespace' property
	 * 
	 * @return defaultNamespace property
	 */
	public String getDefaultNamespace() {
		return defaultNamespace;
	}

	/**
	 * Sets value of 'defaultNamespace' property to specified value.
	
	 * @param defaultNamespace New value.
	 */
	public void setDefaultNamespace(String defaultNamespace) {
		this.defaultNamespace = defaultNamespace;
	}
}
